"""视频分析模块

使用智谱视觉API对视频文件进行内容分析
"""

import os
import time
import logging
import json
from typing import Dict, Optional, Any, List

from zhipuai import ZhipuAI
from config.encoding_categories import ENCODING_CATEGORIES
from config.prompts import VIDEO_ANALYSIS_PROMPT
from .utils import ensure_directory, read_file, extract_video_id

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class VideoAnalyzer:
    """视频分析器类，负责调用智谱视觉API进行视频内容分析"""
    
    def __init__(self, api_key: str = None, api_url: str = None, max_retries: int = None, retry_delay: int = None, model_name: str = None):
        """初始化视频分析器
        
        Args:
            api_key: 智谱API密钥（优先使用环境变量）
            api_url: 智谱API地址（优先使用环境变量）
            max_retries: 最大重试次数（优先使用环境变量）
            retry_delay: 重试间隔（秒）（优先使用环境变量）
            model_name: 智谱视觉模型名称（默认使用GLM-4.5V）
        """
        # 从环境变量或参数中获取配置
        self.api_key = api_key or os.getenv("ZHIPUAI_API_KEY", "demo_api_key_for_development")
        self.api_url = api_url or os.getenv("ZHIPUAI_API_URL", None)
        self.max_retries = max_retries or int(os.getenv("API_ZHIPUAI_MAX_RETRIES", "3"))
        self.retry_delay = retry_delay or int(os.getenv("API_ZHIPUAI_RETRY_DELAY", "5"))
        self.max_tokens = int(os.getenv("API_ZHIPUAI_MAX_TOKENS", "1024"))
        self.model_name = model_name or os.getenv("ZHIPUAI_MODEL_NAME", "GLM-4.5V")
        
        # 初始化ZhipuAI客户端
        if self.api_url:
            self.client = ZhipuAI(api_key=self.api_key, base_url=self.api_url)
        else:
            self.client = ZhipuAI(api_key=self.api_key)
        
        logger.info(f"视频分析器初始化完成，使用模型: {self.model_name}")
    
    def analyze_video(self, video_path: str) -> Optional[Dict[str, Any]]:
        """分析单个视频文件
        
        Args:
            video_path: 视频文件路径
        
        Returns:
            分析结果字典，如果分析失败则返回None
        """
        if not os.path.exists(video_path):
            logger.error(f"视频文件不存在: {video_path}")
            return None
        
        video_id = extract_video_id(os.path.basename(video_path))
        logger.info(f"开始分析视频: {video_path} (ID: {video_id})")
        
        # 定义可用的模型列表，按优先级排序
        available_models = ["GLM-4.5V", "GLM-4.1V-Thinking-Flash"]
        current_model_idx = 0
        
        while current_model_idx < len(available_models):
            # 获取当前要使用的模型
            current_model = available_models[current_model_idx]
            logger.info(f"尝试使用模型: {current_model}")
            
            # 准备视频数据为base64
            try:
                import base64
                with open(video_path, "rb") as f:
                    video_data = base64.b64encode(f.read()).decode('utf-8')
                logger.debug(f"视频文件成功编码为base64，大小: {len(video_data)} 字符")
            except Exception as e:
                logger.error(f"无法读取或编码视频文件: {str(e)}")
                current_model_idx += 1
                continue
            
            # 根据模型构建适当的提示词
            prompt = self._build_prompt_for_model(current_model)
            
            # 构建消息
            messages = [{
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt},
                    {"type": "video", "video": {"url": f"data:video/mp4;base64,{video_data}"}}
                ]
            }]
            
            # 执行分析（带重试机制）
            for attempt in range(self.max_retries):
                try:
                    logger.debug(f"调用智谱API，使用模型: {current_model}，尝试 {attempt + 1}/{self.max_retries}")
                    
                    # 使用SDK调用API
                    response = self.client.chat.completions.create(
                        model=current_model,
                        messages=messages,
                        max_tokens=self.max_tokens,
                        temperature=0.7
                    )
                    
                    # 处理响应
                    if response and response.choices:
                        text = response.choices[0].message.content
                        logger.debug(f"API响应成功，内容长度: {len(text)} 字符")
                        
                        # 解析结果
                        parsed_result = self._parse_response(text)
                        if parsed_result:
                            # 添加基本信息
                            parsed_result['视频文件'] = os.path.basename(video_path)
                            parsed_result['视频ID'] = video_id
                            logger.info(f"使用模型 {current_model} 视频分析成功: {video_id}")
                            return parsed_result
                    
                except Exception as e:
                    # 添加详细的异常信息日志
                    logger.error(f"API调用失败 (模型: {current_model}, 尝试 {attempt + 1}/{self.max_retries})")
                    logger.error(f"异常类型: {type(e).__name__}")
                    logger.error(f"异常消息: {str(e)}")
                    logger.debug(f"完整异常信息:", exc_info=True)
                    
                    # 分析常见错误类型
                    if 'Connection' in str(type(e).__name__) or 'Timeout' in str(type(e).__name__):
                        logger.warning("可能是网络连接问题，请检查网络连接和防火墙设置")
                    elif 'Authentication' in str(e).lower() or 'API key' in str(e):
                        logger.warning("可能是API密钥问题，请检查ZHIPUAI_API_KEY是否正确")
                    elif 'Model' in str(e).lower() or 'model' in str(e).lower():
                        logger.warning("可能是模型名称问题，请检查ZHIPUAI_MODEL_NAME是否支持")
                    
                    if attempt < self.max_retries - 1:
                        sleep_time = self.retry_delay * (2 ** attempt)  # 指数退避
                        logger.info(f"将在 {sleep_time} 秒后重试...")
                        time.sleep(sleep_time)
                    else:
                        logger.error(f"达到最大重试次数，尝试切换到下一个模型")
                        break
            
            # 尝试下一个模型
            current_model_idx += 1
        
        logger.error(f"所有模型都分析失败: {video_path}")
        # 返回空结果字典而不是None，避免后续处理出错
        empty_result = self._get_empty_result()
        empty_result['视频文件'] = os.path.basename(video_path)
        empty_result['视频ID'] = video_id
        empty_result['分析状态'] = '失败'
        empty_result['错误信息'] = '所有模型分析失败'
        return empty_result
    
    def _build_prompt_for_model(self, model_name: str) -> str:
        """根据模型构建适当的提示词
        
        Args:
            model_name: 模型名称
        
        Returns:
            构建好的提示词
        """
        # 针对不同模型使用不同的提示词格式
        if model_name == 'GLM-4.1V-Thinking-Flash':
            # 为GLM-4.1V-Thinking-Flash使用更简洁的提示词
            duration_opts = "、".join(list(ENCODING_CATEGORIES.get('视频时长', {}).keys()) or ['≤1分钟','1-5分钟','5-30分钟','>30分钟'])
            theme_opts = "、".join(list(ENCODING_CATEGORIES.get('视频主题', {}).keys()))
            people_opts = "、".join(list(ENCODING_CATEGORIES.get('出境人物', {}).keys()))
            
            return "\n".join([
                "你是短视频内容标注助手。只输出以下9行字段,严格遵循格式,不得输出任何分析、解释、思考或额外文字。",
                "注意:没有提供视频标题,请根据视频内容推断标题相关特征。",
                f"视频时长: {duration_opts} 中选择一项",
                f"视频主题: {theme_opts} 中选择一项",
                f"出境人物: {people_opts} 中选择一项",
                "视听符号: 按此字典格式输出:{'{'}'场景符号': '选择一项', '音乐符号': '选择一项', '字幕符号': '选择一项或无'{'}'};",
                f"感情色彩: {'、'.join(list(ENCODING_CATEGORIES.get('感情色彩', {}).keys()))} 中选择一项",
                f"语言风格: {'、'.join(list(ENCODING_CATEGORIES.get('语言风格', {}).keys()))} 中选择一项",
                f"标题情感表达: {'、'.join(list(ENCODING_CATEGORIES.get('标题情感表达', {}).keys()))} 中选择一项",
                f"标题平台话题: {'、'.join(list(ENCODING_CATEGORIES.get('标题平台话题', {}).keys()))} 中选择一项",
                "视频摘要: 用100-200字中文客观概述视频内容",
                "输出规则:",
                "- 仅输出上述9行,每行严格为'字段名: 值'格式;值只能是一个选项或上述字典,不可包含任何解释性文字。",
                "- 禁止出现思考用语(如:可能、因为、推断、猜测、疑问句等),禁止使用问号或括号。",
                "- 不得输出JSON、代码块、列表或任何多余内容。",
                "- 无法确定时选择最可能的单一选项。",
            ])
        else:
            # 默认使用标准提示词
            return VIDEO_ANALYSIS_PROMPT
    
    def _parse_response(self, text: str) -> Dict[str, Any]:
        """解析API响应文本
        
        Args:
            text: API返回的文本内容
        
        Returns:
            解析后的结构化结果
        """
        try:
            # 尝试从代码块中提取JSON
            if '```json' in text:
                json_str = text.split('```json')[1].split('```')[0]
            elif '```' in text:
                json_str = text.split('```')[1]
            else:
                json_str = text
            
            # 尝试解析为JSON
            try:
                parsed_content = json.loads(json_str.strip())
                result = self._validate_and_normalize_result(parsed_content)
                return result
            except (ValueError, SyntaxError):
                # JSON解析失败，尝试手动提取
                return self._extract_fields_manually(text)
        except Exception as e:
            logger.error(f"解析响应时出错: {str(e)}")
            return self._get_empty_result()
    
    def _validate_and_normalize_result(self, result: Dict[str, Any]) -> Dict[str, Any]:
        """验证并规范化分析结果
        
        Args:
            result: 原始分析结果
        
        Returns:
            验证和规范化后的结果
        """
        normalized = {}
        
        # 检查每个编码类目的结果
        for category, config in ENCODING_CATEGORIES.items():
            if category in result:
                value = result[category]
                # 如果值是一个完整的字符串，尝试匹配预定义的选项
                if isinstance(value, str):
                    # 查找最匹配的预定义选项
                    best_match = self._find_best_match(value, config['options'], config['keywords'])
                    normalized[category] = best_match or value
                else:
                    normalized[category] = value
            else:
                # 如果缺少某个类别的结果，设为'其他'
                normalized[category] = '其他'
        
        return normalized
    
    def _find_best_match(self, text: str, options: List[str], keywords: Dict[str, List[str]]) -> Optional[str]:
        """根据文本内容查找最佳匹配的选项
        
        Args:
            text: 要匹配的文本
            options: 可选的类别标签
            keywords: 每个选项对应的关键词字典
        
        Returns:
            最佳匹配的选项，如果没有匹配则返回None
        """
        # 检查文本是否直接包含某个选项
        for option in options:
            if option in text:
                return option
        
        # 使用关键词进行匹配
        scores = {}
        for option, option_keywords in keywords.items():
            score = sum(1 for keyword in option_keywords if keyword in text)
            if score > 0:
                scores[option] = score
        
        # 返回得分最高的选项
        if scores:
            return max(scores, key=scores.get)
        
        return None
    
    def _extract_fields_manually(self, text: str) -> Dict[str, Any]:
        """手动从文本中提取字段值
        
        Args:
            text: API返回的文本内容
        
        Returns:
            提取的结构化结果
        """
        def select_by_option_names(field: str, raw: str) -> Optional[str]:
            """根据选项名匹配值"""
            opts = list(ENCODING_CATEGORIES.get(field, {}).keys())
            for o in opts:
                if o and o in raw:
                    return o
            return None
        
        def select_by_keywords(field: str, raw: str) -> Optional[str]:
            """根据关键词匹配值"""
            for opt, kws in ENCODING_CATEGORIES.get(field, {}).items():
                if isinstance(kws, list):
                    for kw in kws:
                        if kw and kw in raw:
                            return opt
            return None
        
        def normalize_single(field: str, raw: str, default_val: str) -> str:
            """规范化单个字段的值"""
            return select_by_option_names(field, raw) or select_by_keywords(field, raw) or default_val
        
        result: Dict[str, str] = {}
        fields = list(ENCODING_CATEGORIES.keys())
        
        # 粗提取每一行的原始文本
        raw_map: Dict[str, str] = {}
        for field in fields:
            if field in text:
                start = text.find(field) + len(field)
                # 查找冒号位置
                colon_pos = text.find(':', start)
                if colon_pos == -1:
                    colon_pos = text.find('：', start)
                if colon_pos != -1:
                    start = colon_pos + 1
                    # 查找下一行的开始
                    end = text.find('\n', start)
                    if end == -1:
                        end = len(text)
                    raw_map[field] = text[start:end].strip()
        
        # 规范化各字段
        for field in fields:
            if field in raw_map:
                # 针对视听符号特殊处理
                if field == '视听符号':
                    av_raw = raw_map[field]
                    # 简化处理，提取场景、音乐、字幕信息
                    scene_val = '其他'
                    music_val = '其他'
                    subtitle_val = '无'
                    
                    if '场景' in av_raw or '画面' in av_raw:
                        scene_val = select_by_keywords('视听符号', av_raw) or '其他'
                    if '音乐' in av_raw:
                        music_val = '背景音乐' if '背景' in av_raw else '其他'
                    if '字幕' in av_raw:
                        subtitle_val = '字幕'
                    
                    result['视听符号'] = {
                        '场景符号': scene_val,
                        '音乐符号': music_val,
                        '字幕符号': subtitle_val
                    }
                else:
                    result[field] = normalize_single(field, raw_map[field], '其他')
            else:
                result[field] = '其他'
        
        # 确保视频摘要存在
        if '视频摘要' not in result or not result['视频摘要'] or result['视频摘要'] == '其他':
            # 尝试从文本末尾提取摘要
            if '视频摘要' in text:
                start = text.find('视频摘要') + 5
                colon_pos = text.find(':', start)
                if colon_pos == -1:
                    colon_pos = text.find('：', start)
                if colon_pos != -1:
                    result['视频摘要'] = text[colon_pos + 1:].strip()
            else:
                result['视频摘要'] = '视频内容未能成功提取'
        
        return result
    
    def _get_empty_result(self) -> Dict[str, Any]:
        """获取空的结果结构
        
        Returns:
            包含所有编码类目的空结果
        """
        return {category: '其他' for category in ENCODING_CATEGORIES.keys()}